﻿// Copyright (c)  MOKEYISH. All rights reserved.
// Licensed under the MIT License, See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using NSQL.Expressions;
using NSQL.Expressions.Functions;
using NSQL.Extensions;
using NSQL.Generator.Formatters;
using NSQL.LogicalResource;
using NSQL.Statements;
using NSQL.Statements.SelectStatements;

#if NETSTANDARD1_6 || NETSTANDARD2_0
using NSQL.Sharpen;
#endif

namespace NSQL.Generator
{
    public abstract class BaseGenerator : ISqlGenerator, IExpressionVisitor<StringBuilder>
    {
        protected SqlGeneratorSettings Settings { get; }
        protected ILogicalDatabase LogicalDatabase => Settings.Database;
        protected bool Optimized => Settings.Optimized;
        protected KeywordStyle KeywordStyle => Settings.KeywordStyle;
        protected bool BraceJoin => Settings.BraceJoin;
        public string Generate(IStatement statement) => statement.Accept(this);

        public BaseGenerator(SqlGeneratorSettings settings)
        {
            Settings = settings ?? throw new ArgumentNullException(nameof(settings));
        }

        public string Generate(ISelectStatement statement) => InternalGenerate(statement).ToString();
        string ISqlGenerator.Generate(UnionStatement statement)
        {
            var builder = new StringBuilder();
            builder.Append(statement.Left.Accept(this));
            builder.Append(' ').Append(Union);
            if (statement.Type == UnionType.All)
            {
                builder.Append(' ').Append(All);
            }

            builder.Append(' ').Append(statement.Right.Accept(this));
            return builder.ToString();
        }

        string ISqlGenerator.Generate(IntersectionStatement statement)
        {
            var builder = new StringBuilder();
            builder.Append(statement.Left.Accept(this));
            builder.Append(' ').Append(Intersect).Append(' ');
            builder.Append(statement.Right.Accept(this));
            return builder.ToString();
        }

        string ISqlGenerator.Generate(MinusStatement statement)
        {
            var builder = new StringBuilder();
            builder.Append(statement.Left.Accept(this));
            builder.Append(' ').Append(Minus).Append(' ');
            builder.Append(statement.Right.Accept(this));
            return builder.ToString();
        }

        string ISqlGenerator.Generate(IExpression expression)
        {
            return expression.Accept(this).ToString();
        }

        private StringBuilder InternalGenerate(ISelectStatement statement)
        {
            while (true)
            {
                if (Optimized && statement.CanOptimized)
                {
                    Debug.Assert(statement.Elements.Count == 1);
                    Debug.Assert(statement.TableSources.Count == 1);
                    Debug.Assert(statement.TableSources[0] is ISelectStatement);
                    Debug.Assert(statement.Skip <= 0);
                    Debug.Assert(statement.Limit <= 0);
                    Debug.Assert(statement.Condition is null);
                    statement = (ISelectStatement) statement.TableSources[0];
                    continue;
                }

                var select = BuildSelect(statement.Elements);
                var from = BuildFrom(statement.TableSources);
                var where = BuildWhere(statement.Condition);

                StringBuilder group;
                StringBuilder having;
                if (statement.GroupClause != null)
                {
                    group = BuildGroup(statement.GroupClause);

                    having = statement.GroupClause.Having is null ? null : BuilderHaving(statement.GroupClause.Having);
                }
                else
                {
                    group = null;
                    having = null;
                }


                var order = BuilderOrder(statement.Order);
                return FormatSelect(select, from, where, group, order, having, statement.Skip, statement.Limit);
            }
        }

        protected virtual string Format(string word)
        {
            if (string.IsNullOrEmpty(word)) return word;
            switch (KeywordStyle)
            {
                case KeywordStyle.None: return word;
                case KeywordStyle.UpperCase: return word.ToUpper();
                case KeywordStyle.LowerCase: return word.ToLower();
                case KeywordStyle.CamelCase: return word.ToCamelCase();
                case KeywordStyle.PascalCase: return word.ToPascalCase();
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        #region [Keywords]
        protected virtual string As => Format(nameof(As));
        protected virtual string Group => Format(nameof(Group));
        protected virtual string From => Format(nameof(From));
        protected virtual string Left => Format(nameof(Left));
        protected virtual string Cast => Format(nameof(Cast));
        protected virtual string Right => Format(nameof(Right));
        protected virtual string Outer => Format(nameof(Outer));
        protected virtual string Is => Format(nameof(Is));
        protected virtual string Null => Format(nameof(Null));
        protected virtual string Select => Format(nameof(Select));
        protected virtual string Where => Format(nameof(Where));
        protected virtual string By => Format(nameof(By));
        protected virtual string Order => Format(nameof(Order));
        protected virtual string Now => Format("Now()");
        protected virtual string ToChar => Format(nameof(ToChar));
        protected virtual string With => Format(nameof(With));
        protected virtual string Having => Format(nameof(Having));
        protected virtual string Inner => Format(nameof(Inner));
        protected virtual string Skip => Format(nameof(Skip));
        protected virtual string Limit => Format(nameof(Limit));
        protected virtual string Year => Format(nameof(Year));
        protected virtual string Month => Format(nameof(Month));
        protected virtual string Week => Format(nameof(Week));
        protected virtual string Day => Format(nameof(Day));
        protected virtual string Hour => Format(nameof(Hour));
        protected virtual string Minute => Format(nameof(Minute));
        protected virtual string Second => Format(nameof(Second));
        protected virtual string MilliSecond => Format(nameof(MilliSecond));
        protected virtual string Years => Format(nameof(Years));
        protected virtual string Months => Format(nameof(Months));
        protected virtual string Weeks => Format(nameof(Weeks));
        protected virtual string Days => Format(nameof(Days));
        protected virtual string Hours => Format(nameof(Hours));
        protected virtual string Minutes => Format(nameof(Minutes));
        protected virtual string Seconds => Format(nameof(Seconds));
        protected virtual string MilliSeconds => Format(nameof(MilliSeconds));
        protected virtual string Interval => Format(nameof(Interval));
        protected virtual string Join => Format(nameof(Join));
        protected virtual string On => Format(nameof(On));
        protected virtual string In => Format(nameof(In));
        protected virtual string Distinct => Format(nameof(Distinct));
        protected virtual string Not => Format(nameof(Not));
        protected virtual string Case => Format(nameof(Case));
        protected virtual string When => Format(nameof(When));
        protected virtual string End => Format(nameof(End));
        protected virtual string Else => Format(nameof(Else));
        protected virtual string Then => Format(nameof(Then));
        protected virtual string Like => Format(nameof(Like));
        protected virtual string StdDev => Format(nameof(StdDev));
        protected virtual string Variance => Format(nameof(Variance));
        protected virtual string Avg => Format(nameof(Avg));
        protected virtual string Count => Format(nameof(Count));
        protected virtual string Max => Format(nameof(Max));
        protected virtual string Min => Format(nameof(Min));
        protected virtual string Trim => Format(nameof(Trim));
        protected virtual string TrimStart => Format(nameof(TrimStart));
        protected virtual string TrimEnd => Format(nameof(TrimEnd));
        protected virtual string Start => Format(nameof(Start));
        protected virtual string PadLeft => Format(nameof(PadLeft));
        protected virtual string PadRight => Format(nameof(PadRight));
        protected virtual string Asc => Format(nameof(Asc));
        protected virtual string Desc => Format(nameof(Desc));
        protected virtual string And => Format(nameof(And));
        protected virtual string Or => Format(nameof(Or));
        protected virtual string Between => Format(nameof(Between));
        protected virtual string ToNumber => Format(nameof(ToNumber));
        protected virtual string Round => Format(nameof(Round));
        protected virtual string Sum => Format(nameof(Sum));
        protected virtual string Length => Format(nameof(Length));
        protected virtual string Extract => Format(nameof(Extract));
        protected virtual string Upper => Format(nameof(Upper));
        protected virtual string Lower => Format(nameof(Lower));
        protected virtual string Reverse => Format(nameof(Reverse));
        protected virtual string Replace => Format(nameof(Replace));
        protected virtual string Nvl => Format(nameof(Nvl));
        protected virtual string Union => Format(nameof(Union));
        protected virtual string Minus => Format(nameof(Minus));
        protected virtual string Intersect => Format(nameof(Intersect));
        protected virtual string All => Format(nameof(All));
        
        protected virtual string Sign => Format(nameof(Sign));
        protected virtual string Sin => Format(nameof(Sin));
        protected virtual string Cos => Format(nameof(Cos));
        protected virtual string Tan => Format(nameof(Tan));
        protected virtual string Abs => Format(nameof(Abs));
        protected virtual string Concat => Format(nameof(Concat));
        protected virtual string Substr => Format(nameof(Substr));
        
        protected virtual string Exists => Format(nameof(Exists));

        #region [Common]

        
        protected virtual string Both => Format(nameof(Both));
        protected virtual string Leading => Format(nameof(Leading));
        protected virtual string Trailing => Format(nameof(Trailing));

        #endregion
        #endregion
        
        #region [Select]
        private StringBuilder BuildSelect(SelectElementCollection selectElements, StringBuilder builder = null)
        {
            builder = builder ?? new StringBuilder();

            using (var enumerator = selectElements.GetEnumerator())
            {
                if (!enumerator.MoveNext()) return builder;
                var current = enumerator.Current;
                if (current != null) BuildSelectElement(current, builder);
                while (enumerator.MoveNext())
                {
                    current = enumerator.Current;
                    if (current == null) continue;
                    builder.Append(", ");
                    BuildSelectElement(current, builder);
                }

            }
            return builder;
        }

        // ReSharper disable once UnusedMethodReturnValue.Local
        private StringBuilder BuildSelectElement(ISelectElement selectElement, StringBuilder builder = null)
        {
            builder = builder ?? new StringBuilder();
            if (selectElement == SelectElement.Asterisk)
            {
                builder.Append('*');
            }
            else
            {
                builder.Append(selectElement.Element.Accept(this));
            }

            if (string.IsNullOrWhiteSpace(selectElement.Alias)) return builder;

            builder.Append(' ').Append(Format(As));
            builder.Append(' ').Append(selectElement.Alias);
            return builder;
        }

        
        protected virtual StringBuilder FormatSelect(StringBuilder select, StringBuilder from, StringBuilder where,
            StringBuilder group, StringBuilder order, StringBuilder having)
        {
            var sb = new StringBuilder();
            sb.Append(Select).Append(' ');

            if (select == null || select.Length == 0)
            {
                sb.Append('1');
            }
            else
            {
                sb.Append(select);
            }

            if (from?.Length > 0)
            {
                sb.Append(' ').Append(From).Append(' ').Append(from);
            }

            if (where?.Length > 0)
            {
                sb.Append(' ').Append(Where).Append(' ').Append(where);
            }

            if (group?.Length > 0)
            {
                sb.Append(' ').Append(Group).Append(' ').Append(By).Append(' ').Append(group);
            }
            
            
            if (having?.Length > 0)
            {
                sb.Append(' ').Append(Having).Append(' ').Append(having);
            }

            if (order?.Length > 0)
            {
                sb.Append(' ').Append(Order).Append(' ').Append(By).Append(' ').Append(order);
            }

            return sb;
        }
        
        protected virtual StringBuilder FormatSelect(StringBuilder select, StringBuilder from, StringBuilder where, StringBuilder group,
            StringBuilder order, StringBuilder having, int skip, int limit)
        {
            var sqlBuilder = FormatSelect(select, from, where, group, order, having);

            if (skip > 0)
            {
                sqlBuilder.Append(' ');
                sqlBuilder.Append(Skip);
                sqlBuilder.Append(' ');
                sqlBuilder.Append(skip);
            }
            
            if (limit <= 0) return sqlBuilder;
            sqlBuilder.Append(' ');
            sqlBuilder.Append(Limit);
            sqlBuilder.Append(' ');
            sqlBuilder.Append(limit);
            return sqlBuilder;
        }
        #endregion

        #region [From]

        private StringBuilder BuildFrom(TableSourceCollection tableSources, StringBuilder builder = null)
        {
            if (tableSources.Count == 1 && tableSources[0].IsDual)
            {
                return null;
            }
            return (builder ?? new StringBuilder()).AppendJoin(", ", tableSources.Select(o => BuildTableSource(o)));
        }
        
        private StringBuilder BuildTableSource(ITableSource table, StringBuilder builder = null)
        {
            builder = builder ?? new StringBuilder();
            switch (table)
            {
                case TableSource tableSource:
                    if (!string.IsNullOrWhiteSpace(tableSource.Schema))
                    {
                        builder.Append(tableSource.Schema).Append('.');
                    }

                    builder.Append(tableSource.Name);
                    break;
                case ISelectStatement selectStatement:
                    builder.Append('(').Append(InternalGenerate(selectStatement)).Append(')');
                    break;
                default:
                    throw new NotImplementedException();
            }

            if (!string.IsNullOrEmpty(table.Alias))
            {
                builder.Append(' ').Append(table.Alias);
            }
            
            if (table.Join != null)
            {
                BuildJoin(table.Join, builder);
            }
            return builder;
        }

        private StringBuilder BuildJoin(JoinNode join, StringBuilder builder)
        {
            if (join is null) return builder;
            builder = builder ?? new StringBuilder();
            if (join.Previous != null)
            {
                builder = BuildJoin(join.Previous, builder);
            }

            builder.Append(' ');
            switch (join.Type)
            {
                case JoinType.Inner:
                    builder
                        .Append(Inner);
                    break;
                case JoinType.LeftOuter:
                    builder
                        .Append(Left).Append(' ')
                        .Append(Outer);
                    break;
                case JoinType.RightOuter:
                    builder.Append(Right).Append(' ')
                        .Append(Outer);
                    break;
                case JoinType.Left:
                    builder
                        .Append(Left);
                    break;
                case JoinType.Right:
                    builder.Append(Right);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append(' ').Append(Join).Append(' ');

            BuildTableSource(join.JoinTable, builder);


            builder.Append(' ').Append(On).Append(' ');
            
            
            if (BraceJoin)
            {
                builder.Append('(');
            }

            builder.Append(join.Condition?.Accept(this));
            
            if (BraceJoin)
            {
                builder.Append(')');
            }

            return builder;
        }

        #endregion

        #region [Where]
        // ReSharper disable once SuggestBaseTypeForParameter
        private StringBuilder BuildWhere(ILogicalExpression expression) => expression?.Accept(this);
        #endregion

        #region [Group]
        private StringBuilder BuildGroup(GroupCollection expressions, StringBuilder builder = null)
        {
            builder = builder ?? new StringBuilder();
            builder.AppendJoin(", ", expressions.Select(o => o.Accept(this)));
            return builder;
        }
        #endregion

        #region [Order]

        private StringBuilder BuilderOrder(SortingCollection sortingCollection, StringBuilder builder = null)
        {
            builder = builder ?? new StringBuilder();
            using (var enumerator = sortingCollection.GetEnumerator())
            {
                if (!enumerator.MoveNext()) return builder;
                var current = enumerator.Current;
                BuilderOrder(current, builder);

                while (enumerator.MoveNext())
                {
                    current = enumerator.Current;
                    if (current == null) continue;
                    builder.Append(", ");
                    BuilderOrder(current, builder);
                }
            }
            return builder;
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        // ReSharper disable once UnusedMethodReturnValue.Local
        private StringBuilder BuilderOrder(ISortingElement sortingElement, StringBuilder builder = null)
        {
            if (sortingElement is null) return builder;
            builder = builder ?? new StringBuilder();
            builder.Append(sortingElement.Element.Accept(this));
            switch (sortingElement.Direction)
            {
                case SortingDirection.None:
                    break;
                case SortingDirection.Ascending:
                    builder.Append(' ').Append(Asc);
                    break;
                case SortingDirection.Descending:
                    builder.Append(' ').Append(Desc);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return builder;
        }

        #endregion
        
        #region [Having]

        // ReSharper disable once SuggestBaseTypeForParameter
        private StringBuilder BuilderHaving(ILogicalExpression logicalExpression)
        {
            return logicalExpression?.Accept(this);
        }

        #endregion

        #region [Expression]

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IComparisonExpression comparisonExpression)
        {
            var builder = new StringBuilder();
            builder.Append(comparisonExpression.Left.Accept(this));
            builder.Append(' ');
            switch (comparisonExpression.Operator)
            {
                case ComparisonOperator.Equal:
                    builder.Append('=');
                    break;
                case ComparisonOperator.NotEqual:
                    builder.Append("!=");
                    break;
                case ComparisonOperator.Less:
                    builder.Append('<');
                    break;
                case ComparisonOperator.Greater:
                    builder.Append('>');
                    break;
                case ComparisonOperator.LessOrEqual:
                    builder.Append("<=");
                    break;
                case ComparisonOperator.GreaterOrEqual:
                    builder.Append(">=");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(' ');
            builder.Append(comparisonExpression.Right.Accept(this));
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IColumnExpression columnExpression)
        {
            var builder = new StringBuilder();
            var columnName = columnExpression.Name;
            if (columnExpression.Id > 0)
            {
                var column = LogicalDatabase?.FindColumnById(columnExpression.Id);
                if (column != null)
                {
                    columnName = column.Name;
                }
            }
            
            if (columnExpression.Delimited)
            {
                builder.Append('"');
                builder.Append(columnName);
                builder.Append('"');
            }
            else
            {
                builder.Append(columnName);
            }
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IConstantExpression constantExpression)
        {
            var builder = new StringBuilder();
            var value = constantExpression.ValueOf();
            switch (value)
            {
                case null:
                    builder.Append("<null>");
                    break;
                case string str:
                    builder.Append('\'');
                    builder.Append(str);
                    builder.Append('\'');
                    break;
                case DateTime dateTime:
                    builder.Append('#');
                    builder.Append(dateTime.ToString(Settings.CultureInfo));
                    builder.Append('#');
                    break;
                case bool b:
                    return new StringBuilder(b ? "True" : "False");
                default:
                    return new StringBuilder(value.ToString());
            }
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IInExpression inExpression)
        {
            return new StringBuilder($"{inExpression.Left.Accept(this)} In ({inExpression.Elements.Accept(this)})");
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(INotInExpression notInExpression)
        {
            return new StringBuilder($"{notInExpression.Left.Accept(this)} Not In ({notInExpression.Elements.Accept(this)})");
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IGroupExpression groupExpression)
        {
            var builder = groupExpression.Left.Accept(this);
            builder.Append(' ').Append(Format(groupExpression.Operator.ToString())).Append(' ');
            
            var isGroupExpression = groupExpression.Right is IGroupExpression;
            if (isGroupExpression) builder.Append('(');
            builder.Append(groupExpression.Right.Accept(this));
            if (isGroupExpression) builder.Append(')');
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IBetweenExpression betweenExpression)
        {
            var builder = betweenExpression.LeftExpression.Accept(this);
            builder.Append(' ').Append(Between);
            builder.Append(' ').Append(betweenExpression.BeginExpression.Accept(this));
            builder.Append(' ').Append(And);
            builder.Append(' ').Append(betweenExpression.EndExpression.Accept(this));
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(INotExpression notExpression)
        {
            var builder = new StringBuilder();
            builder.Append(Not);
            var isGroupExpression = notExpression.Expression is IGroupExpression;
            if (isGroupExpression)
            {
                builder.Append('(');
            }
            else
            {
                builder.Append(' ');
            }
            builder.Append(notExpression.Expression.Accept(this));
            if (isGroupExpression) builder.Append(')');
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(INullExpression nullExpression)
        {
            var builder = nullExpression.Expression.Accept(this);
            builder.Append(' ').Append(Is);
            builder.Append(' ').Append(Null);
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(INotNullExpression notNullExpression)
        {
            var builder = notNullExpression.Expression.Accept(this);
            builder.Append(' ').Append(Is);
            builder.Append(' ').Append(Not);
            builder.Append(' ').Append(Null);
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IMatchExpression matchExpression)
        {
            var builder = new StringBuilder();
            builder.Append(Case);
            foreach (var match in matchExpression.Matches)
            {
                builder.Append(' ').Append(When);
                builder.Append(' ').Append(match.When.Accept(this));
                builder.Append(' ').Append(Then);
                builder.Append(' ').Append(match.Then.Accept(this));
            }

            builder.Append(' ').Append(Else);
            builder.Append(' ').Append(matchExpression.Default.Accept(this));
            builder.Append(' ').Append(End);
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(ISimpleMatchExpression simpleMatchExpression)
        {
            var builder = new StringBuilder();
            builder.Append(Case);
            builder.Append(' ').Append(simpleMatchExpression.Value.Accept(this));
            foreach (var match in simpleMatchExpression.Matches)
            {
                builder.Append(' ').Append(When);
                builder.Append(' ').Append(match.When.Accept(this));
                builder.Append(' ').Append(Then);
                builder.Append(' ').Append(match.Then.Accept(this));
            }

            builder.Append(' ').Append(Else);
            builder.Append(' ').Append(simpleMatchExpression.Default.Accept(this));
            builder.Append(' ').Append(End);
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IArithmeticExpression arithmeticExpression)
        {
            var builder = new StringBuilder();

            if (Optimized && arithmeticExpression.TryEval(out var result))
            {
                return result.Accept(this);
            }

            if (arithmeticExpression.LeftExpression is IArithmeticExpression leftExpression &&
                (arithmeticExpression.Operator == ArithmeticOperator.Multiply || arithmeticExpression.Operator == ArithmeticOperator.Divide) &&
                (leftExpression.Operator == ArithmeticOperator.Plus || leftExpression.Operator == ArithmeticOperator.Minus))
            {
                builder.Append('(').Append(arithmeticExpression.LeftExpression?.Accept(this)).Append(')');
            }
            else
            {
                
                builder.Append(arithmeticExpression.LeftExpression?.Accept(this));
            }

            switch (arithmeticExpression.Operator)
            {
                case ArithmeticOperator.Plus:
                    if (Optimized &&
                        arithmeticExpression.RightExpression is IConstantNumericExpression constantNumeric0 && constantNumeric0.IsZero)
                    {
                        return builder;
                    }
                    builder.Append(" + ");
                    break;
                case ArithmeticOperator.Minus:
                    if (Optimized &&
                        arithmeticExpression.RightExpression is IConstantNumericExpression constantNumeric1 && constantNumeric1.IsZero)
                    {
                        return builder;
                    }
                    builder.Append(" - ");
                    break;
                case ArithmeticOperator.Multiply:
                    builder.Append(" * ");
                    break;
                case ArithmeticOperator.Divide:
                    builder.Append(" / ");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            if (arithmeticExpression.RightExpression is IArithmeticExpression rightExpression &&
                (rightExpression.Operator == ArithmeticOperator.Plus || rightExpression.Operator == ArithmeticOperator.Minus))
            {
                return builder.Append('(').Append(arithmeticExpression.RightExpression.Accept(this)).Append(')');
            }

            return builder.Append(arithmeticExpression.RightExpression.Accept(this));
        }

        public StringBuilder Visit(IUnaryExpression unaryExpression)
        {
            var builder = new StringBuilder();
            switch (unaryExpression.Operator)
            {
                case UnaryOperator.Plus:
                    builder.Append('+');
                    break;
                case UnaryOperator.Minus:
                    builder.Append('-');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append(unaryExpression.Accept(this));
            return builder;
        }

        public StringBuilder Visit(ISystemVariable systemVariable)
        {
            switch (systemVariable)
            {
                case NowFunction _:
                    return new StringBuilder(Now);
                default:
                    throw new NotImplementedException();
            }
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(ILikeExpression likeExpression)
        {
            var builder = likeExpression.LeftExpression.Accept(this);
            builder.Append(' ').Append(Like);
            builder.Append(' ').Append(likeExpression.PatternExpression.Accept(this));
            return builder;
        }

        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IExpressions inElements)
        {
            var builder = new StringBuilder();
            using (var enumerator = inElements.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    throw new Exception("Append [IExpressions] error.");
                }

                var current = enumerator.Current;
                builder.Append(current);

                while (enumerator.MoveNext())
                {
                    builder.Append(", ");
                    current = enumerator.Current;
                    builder.Append(current.Accept(this));
                }
            }

            return builder;
        }

        public StringBuilder Visit(IExistsExpression existsExpression)
        {
            return new StringBuilder()
                .Append(Exists)
                .Append('(')
                .Append(Generate(existsExpression.DataSet))
                .Append(')');
        }

        #region [Function]

        IFunctionVisitor<StringBuilder> IExpressionVisitor<StringBuilder>.FunctionVisitor => this;

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICountFunction countFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Count);
            builder.Append('(');
            switch (countFunction.Type)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    builder.Append(Distinct).Append(' ');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(countFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IRoundFunction roundFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Round);
            builder.Append('(');
            builder.Append(roundFunction.Expr.Accept(this));
            if (!(roundFunction.Decimals is null))
            {
                var decimals = roundFunction.Decimals.Accept(this).ToString();
                if (decimals != "0")
                {
                    builder.Append(", ").Append(decimals);
                }
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ISumFunction sumFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Sum);
            builder.Append('(');
            switch (sumFunction.Type)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    builder.Append(Distinct).Append(' ');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(sumFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IMaxFunction maxFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Max);
            builder.Append('(');
            switch (maxFunction.Type)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    builder.Append(Distinct).Append(' ');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(maxFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAvgFunction avgFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Avg);
            builder.Append('(');
            switch (avgFunction.Type)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    builder.Append(Distinct).Append(' ');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(avgFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IMinFunction minFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Min);
            builder.Append('(');
            switch (minFunction.Type)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    builder.Append(Distinct).Append(' ');
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(minFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IStdDevFunction stdDevFunction)
        {
            var builder = new StringBuilder();
            builder.Append(StdDev);
            builder.Append('(');
            builder.Append(stdDevFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IVarianceFunction varianceFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Variance);
            builder.Append('(');
            builder.Append(varianceFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IToCharFunction toCharFunction)
        {
            var builder = new StringBuilder();
            builder.Append(ToChar);
            builder.Append('(');
            builder.Append(toCharFunction.Expr.Accept(this));
            if (!(toCharFunction.Formatter is null))
            {
                builder.Append(", ");
                builder.Append(toCharFunction.Formatter?.Accept(this));
            }

            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ITrimFunction trimFunction)
        {
            var hasTrimExpr = !(trimFunction.TrimExpr is null);

            var  builder =  new StringBuilder();
            if (hasTrimExpr)
            {
                builder.Append(Trim);
            }
            else
            {
                switch (trimFunction.Type)
                {
                    case TrimType.Both:
                        builder.Append(Trim);
                        break;
                    case TrimType.Leading:
                        builder.Append(TrimStart);
                        break;
                    case TrimType.Trailing:
                        builder.Append(TrimEnd);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            
            builder.Append('(');


            if (hasTrimExpr)
            {
                switch (trimFunction.Type)
                {
                    case TrimType.Both:
                        builder.Append(Both);
                        break;
                    case TrimType.Leading:
                        builder.Append(Leading);
                        break;
                    case TrimType.Trailing:
                        builder.Append(Trailing);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                
                
                builder.Append(' ').Append(trimFunction.TrimExpr.Accept(this));
                builder.Append(' ').Append(From).Append(' ');
            }

            builder.Append(trimFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ILengthFunction lengthFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Length);
            builder.Append('(').Append(lengthFunction.Expr.Accept(this)).Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICastFunction castFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Cast);
            builder.Append('(');
            builder.Append(castFunction.Expr.Accept(this));
            builder.Append(' ').Append(As).Append(' ');
            builder.Append(castFunction.TargetType);
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IToNumberFunction toNumberFunction)
        {
            var  builder = new StringBuilder();
            builder.Append(ToNumber);
            builder.Append('(');
            builder.Append(toNumberFunction.Text.Accept(this));
            if (toNumberFunction.Format != null)
            {
                builder.Append(',');
                builder.Append(' ').Append(toNumberFunction.Format.Accept(this));
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ISubStringFunction stringFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Substr);
            builder.Append('(');
            builder.Append(stringFunction.Text.Accept(this));
            builder.Append(", ").Append(stringFunction.Position.Accept(this));
            if (!(stringFunction.Length is null))
            {
                builder.Append(", ").Append(stringFunction.Length.Accept(this));
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(INvlFunction nvlFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Nvl);
            builder.Append('(');
            builder.Append(nvlFunction.Expr.Accept(this));
            if (!(nvlFunction.Then is null))
            {
                builder.Append(", ").Append(nvlFunction.Then.Accept(this));
            }
            builder.Append(", ").Append(nvlFunction.Default.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IPadFunction padFunction)
        {
            var builder = new StringBuilder();
            switch (padFunction.Type)
            {
                case PaddingType.Left:
                    builder.Append(PadLeft);
                    break;
                case PaddingType.Right:
                    builder.Append(PadRight);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append('(');
            builder.Append(padFunction.Text.Accept(this));
            builder.Append(", ");
            builder.Append(padFunction.Length.Accept(this));
            if (!(padFunction.PaddingText is null))
            {
                builder.Append(", ");
                builder.Append(padFunction.PaddingText.Accept(this));
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ILeftFunction leftFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Left);
            builder.Append('(');
            builder.Append(leftFunction.Text.Accept(this));
            builder.Append(", ").Append(leftFunction.Length.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IRightFunction rightFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Right);
            builder.Append('(');
            builder.Append(rightFunction.Text.Accept(this));
            builder.Append(", ").Append(rightFunction.Length.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IUpperFunction upperFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Upper);
            builder.Append('(').Append(upperFunction.Text.Accept(this)).Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ILowerFunction lowerFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Lower);
            builder.Append('(').Append(lowerFunction.Text.Accept(this)).Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IReplaceFunction replaceFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Replace);
            builder.Append('(');
            builder.Append(replaceFunction.Text.Accept(this));
            builder.Append(", ").Append(replaceFunction.OldValue.Accept(this));
            builder.Append(", ").Append(replaceFunction.NewValue.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IExtractFunction extractFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Extract);
            builder.Append('(');
            switch (extractFunction.Type)
            {
                case ExtractType.Year:
                    builder.Append(Year);
                    break;
                case ExtractType.Month:
                    builder.Append(Month);
                    break;
                case ExtractType.Day:
                    builder.Append(Day);
                    break;
                case ExtractType.Hour:
                    builder.Append(Hour);
                    break;
                case ExtractType.Minute:
                    builder.Append(Minute);
                    break;
                case ExtractType.Second:
                    builder.Append(Second);
                    break;
                case ExtractType.Week:
                    builder.Append(Week);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append(' ').Append(From).Append(' ');
            builder.Append(extractFunction.DateTime.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IConcatFunction concatFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Concat);
            builder.Append('(');
            builder.AppendJoin(", ", concatFunction.Items.Select(o => o.Accept(this)));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IReverseFunction reverseFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Reverse);
            builder.Append('(');
            builder.Append(reverseFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IToDatetimeFunction toDatetimeFunction)
        {
            throw new NotImplementedException();
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAddIntervalFunction addIntervalFunction)
        {
            var builder = new StringBuilder();
            builder.Append(addIntervalFunction.DateTime.Accept(this));
            builder.Append(' ').Append('+').Append(' ');
            builder.Append(Format(Interval));
            builder.Append(' ').Append('\'').Append(addIntervalFunction.Interval.Interval).Append('\'').Append(' ');

            switch (addIntervalFunction.Interval.Type)
            {
                case IntervalType.Years:
                    builder.Append(Format(Year));
                    break;
                case IntervalType.Months:
                    builder.Append(Format(Month));
                    break;
                case IntervalType.Days:
                    builder.Append(Format(Day));
                    break;
                case IntervalType.Hours:
                    builder.Append(Format(Hour));
                    break;
                case IntervalType.Minutes:
                    builder.Append(Format(Minute));
                    break;
                case IntervalType.Seconds:
                    builder.Append(Format(Second));
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IStartWithFunction startWithFunction)
        {
            var builder = new StringBuilder();
            builder.Append(startWithFunction.Expr.Accept(this));
            builder.Append(' ').Append(Like).Append(' ');
            if (startWithFunction.Pattern is IConstantStringExpression pattern)
            {
                builder.Append('\'').Append(pattern.Value).Append('%').Append('\'');
            }
            else
            {
                throw new NotImplementedException();
            }
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IEndWithFunction endWithFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(endWithFunction.Expr.Accept(this));
            builder.Append(' ').Append(Like).Append(' ');
            if (endWithFunction.Pattern is IConstantStringExpression pattern)
            {
                builder.Append('\'').Append('%').Append(pattern.Value).Append('\'');
            }
            else
            {
                throw new NotImplementedException();
            }
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IContainsFunction containsFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(containsFunction.Expr.Accept(this));
            builder.Append(' ').Append(Like).Append(' ');
            if (containsFunction.Pattern is IConstantStringExpression pattern)
            {
                builder.Append('\'').Append('%').Append(pattern.Value).Append('%').Append('\'');
            }
            else
            {
                throw new NotImplementedException();
            }
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ISinFunction sinFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Sin);
            builder.Append('(');
            builder.Append(sinFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICosFunction cosFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Cos);
            builder.Append('(');
            builder.Append(cosFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ITanFunction tanFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Tan);
            builder.Append('(');
            builder.Append(tanFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAbsFunction absFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Abs);
            builder.Append('(');
            builder.Append(absFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ISignFunction signFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Sign);
            builder.Append('(');
            builder.Append(signFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }

        #endregion
        
        #endregion
    }
}